
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
//import java.awt.Graphics2D;
//import java.awt.Image;
import java.awt.Point;
//import java.awt.Polygon;
//import java.awt.RenderingHints;
//import java.awt.event.ActionEvent;
//import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
//import java.awt.font.ShapeGraphicAttribute;
//import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;

import java.io.*;

import javax.sound.sampled.*;

public class ViewPanelGame extends AbstractView {
	//public static final int BLOCK_WIDTH = 40;
	//public static final int BLOCK_HEIGHT = 40;
	
	public static final int PLAY_FIELD_WIDTH = 675;	
	public static final int TABLE_SIZE = 550;	//rectangle
	public static final int TABLE_X = 65;	// starting x position for table 
	public static final int TABLE_Y = 13;	// 			y position for table
	public static final int FIELD_SIZE = 440;
	public static final int BLOCK_SIZE = 55;	//rectangle
	//private Image iBuffer;
	//private Graphics2D gBuffer;
	
	private PanelGame panelGame;
	private ControllerGame controller;
	
	//private Integer round;
	
	private Board board;
	private Integer turn = 0;	// turn start from Black, which is 0;
	private ArrayList<Point> availableBlocks;
	private Integer gameMode;
	private int viewClock;		// To slow the CPU hit speed.
	private Boolean gameover;
	private Integer Turn_count;
	
	/*
	 *  Label and Text Field for Status Field
	 */
	JTextField black_disk = new JTextField(); 
	JTextField white_disk = new JTextField();
	JTextField Turn = new JTextField(); 
	JLabel black_lb = new JLabel("BLACK:");
	JLabel white_lb = new JLabel("WHITE:");
	JLabel turn_lb = new JLabel("Turn:");
	
	// Sound variables
	boolean bgmSoft;
	boolean bgmHard;
	Thread sound_hit;
	Thread bgm;
	Thread sound_gameover;
	boolean hitSuspend;
	
	// Constructor
	public ViewPanelGame(){
		board = new Board();
		availableBlocks = new ArrayList<Point>();
		gameover = new Boolean(false);
		Turn_count = new Integer(1);
		
		// Panel
		panelGame = new PanelGame();
		
		// Sounds
		bgmSoft = true;
		bgmHard = false;
		hitSuspend = true;
		sound_hit = new Thread(new sound_Hit());	
		bgm = new Thread(new sound_BGMsoft());
		sound_gameover = new Thread(new sound_Gameover());		
	}
	
	public class PanelGame extends JPanel implements MouseMotionListener, MouseListener, KeyListener{	
		/*
		 * Key state flags for moving cursor on board
		 */
		private boolean keyState_up, keyState_down;
		private boolean keyState_left, keyState_right;
		/*
		 * Inner class PanelGame constructor
		 */
		public PanelGame(){
			setLayout(null);
			addButtons();
			addKeyListener(this);
			addMouseMotionListener(this);
			addMouseListener(this);
			keyState_up = false;
			keyState_down = false;
			keyState_left = false;
			keyState_right = false;
			
			//repaint = new Thread(new Repaint(this));
			//repaint.start();			
			
			// set Text Field, and Label in Status Area
			
	        black_disk.setBounds(700,450, 100, 25);
	        white_disk.setBounds(700,525, 100, 25);
	        Turn.setBounds(700, 50, 50, 25);
	        
	        /*set label*/
	        black_lb.setBounds(700,400,100,50);
	        white_lb.setBounds(700,475,100,50);
	        turn_lb.setBounds(700, 10, 100, 50);
	        
	        add(black_disk);
	        add(white_disk);
	        add(Turn);
	        
	        add(black_lb);
	        add(white_lb);
	        add(turn_lb);     
	        this.repaint();			
		}
		public void paintComponent(Graphics g) {
	        // Board Area
	        g.setColor(new Color(50, 50, 150));
			g.fillRect(0, 0, PLAY_FIELD_WIDTH, panelGame.getHeight());
			g.setColor(new Color(45,180,45));
			g.draw3DRect(TABLE_X, TABLE_Y, TABLE_SIZE, TABLE_SIZE, true);
			g.fill3DRect(TABLE_X, TABLE_Y, TABLE_SIZE, TABLE_SIZE, true);
			g.setColor(Color.BLACK);
			g.drawRect(TABLE_X+(TABLE_SIZE-FIELD_SIZE)/2,TABLE_Y+(TABLE_SIZE-FIELD_SIZE)/2, FIELD_SIZE, FIELD_SIZE);
			for(int i=0;i<8;i++){
				g.drawLine(TABLE_X+(TABLE_SIZE-FIELD_SIZE)/2, TABLE_Y+(TABLE_SIZE-FIELD_SIZE)/2+BLOCK_SIZE*i,
						TABLE_X+(TABLE_SIZE-FIELD_SIZE)/2+FIELD_SIZE, TABLE_Y+(TABLE_SIZE-FIELD_SIZE)/2+BLOCK_SIZE*i);
				g.drawLine(TABLE_X+(TABLE_SIZE-FIELD_SIZE)/2+BLOCK_SIZE*i, TABLE_Y+(TABLE_SIZE-FIELD_SIZE)/2,
						TABLE_X+(TABLE_SIZE-FIELD_SIZE)/2+BLOCK_SIZE*i, TABLE_Y+(TABLE_SIZE-FIELD_SIZE)/2+FIELD_SIZE);
			}
			
			// Display Black disks
			if(board.getBlack()!=null){
				for(int i=0;i<board.getBlack().size();i++){
					g.setColor(Color.BLACK);
					g.fillRoundRect(TABLE_X+(TABLE_SIZE-FIELD_SIZE)/2+BLOCK_SIZE*(board.getBlack().get(i).x-1),
							TABLE_Y+(TABLE_SIZE-FIELD_SIZE)/2+BLOCK_SIZE*(board.getBlack().get(i).y-1), BLOCK_SIZE, BLOCK_SIZE, 100, 100);
					g.setColor(Color.LIGHT_GRAY);
					g.drawRoundRect(TABLE_X+(TABLE_SIZE-FIELD_SIZE)/2+BLOCK_SIZE*(board.getBlack().get(i).x-1),
							TABLE_Y+(TABLE_SIZE-FIELD_SIZE)/2+BLOCK_SIZE*(board.getBlack().get(i).y-1), BLOCK_SIZE, BLOCK_SIZE, 100, 100);
				}
				
			}
			// Display White disks
			if(board.getWhite()!=null){
				//System.out.println("Loopinng???");
				/*try{
					Thread.sleep(500);
				}catch(InterruptedException e){}*/
				for(int i=0;i<board.getWhite().size();i++){
					g.setColor(Color.WHITE);
					g.fillRoundRect(TABLE_X+(TABLE_SIZE-FIELD_SIZE)/2+BLOCK_SIZE*(board.getWhite().get(i).x-1),
						TABLE_Y+(TABLE_SIZE-FIELD_SIZE)/2+BLOCK_SIZE*(board.getWhite().get(i).y-1), BLOCK_SIZE, BLOCK_SIZE, 100, 100);
					g.setColor(Color.DARK_GRAY);
					g.drawRoundRect(TABLE_X+(TABLE_SIZE-FIELD_SIZE)/2+BLOCK_SIZE*(board.getWhite().get(i).x-1),
							TABLE_Y+(TABLE_SIZE-FIELD_SIZE)/2+BLOCK_SIZE*(board.getWhite().get(i).y-1), BLOCK_SIZE, BLOCK_SIZE, 100, 100);
				}
			}
			    
			// Display available blocks
			if(availableBlocks!=null){
				for(int i=0;i<availableBlocks.size();i++){
					if(turn==0)
						g.setColor(new Color(1,1,1,100));
					else if(turn==1)
						g.setColor(new Color(254,254,254,135));
					g.fillRect(TABLE_X+(TABLE_SIZE-FIELD_SIZE)/2+BLOCK_SIZE*(availableBlocks.get(i).x-1)+1,
							TABLE_Y+(TABLE_SIZE-FIELD_SIZE)/2+BLOCK_SIZE*(availableBlocks.get(i).y-1)+1, BLOCK_SIZE-1, BLOCK_SIZE-1);
				}

			}
			
			// Status Area
			g.setColor(new Color(220,170,70));
			g.fillRect(PLAY_FIELD_WIDTH, 0, this.getWidth()-PLAY_FIELD_WIDTH, panelGame.getHeight());
			black_disk.setText(String.valueOf(board.getBlack().size()));
			white_disk.setText(String.valueOf(board.getWhite().size()));
			Turn.setText(String.valueOf(Turn_count));
	        
			// Game Over
			if(gameover){
				g.setColor(new Color(0,0,0,100));
				g.fillRect(0, 0, 899, 599);
				g.setColor(Color.BLACK);
				g.drawRect(PLAY_FIELD_WIDTH + (this.getWidth()-PLAY_FIELD_WIDTH)/2-200/2, this.getHeight()/2-100/2, 200, 100);
				g.setColor(new Color(220,170,70));
				g.fillRect(PLAY_FIELD_WIDTH + (this.getWidth()-PLAY_FIELD_WIDTH)/2-200/2, this.getHeight()/2-100/2, 200, 100);
				g.setFont(new Font("Kristen ITC", Font.BOLD, 30));
				g.setColor(Color.BLACK);
				g.drawString(""+board.getBlack().size(),
						PLAY_FIELD_WIDTH + (this.getWidth()-PLAY_FIELD_WIDTH)/2-200/2 + 20, this.getHeight()/2-100/2+30);
				g.setColor(Color.BLUE);
				g.drawString(":",
						PLAY_FIELD_WIDTH + (this.getWidth()-PLAY_FIELD_WIDTH)/2-200/2 + 75, this.getHeight()/2-100/2+30);
				g.setColor(Color.WHITE);
				g.drawString(""+board.getWhite().size(),
						PLAY_FIELD_WIDTH + (this.getWidth()-PLAY_FIELD_WIDTH)/2-200/2 + 100, this.getHeight()/2-100/2+30);
				
				if(board.getWinner() == 0){
					g.setColor(Color.BLACK);					
					g.drawString("Black Win!",
							PLAY_FIELD_WIDTH + (this.getWidth()-PLAY_FIELD_WIDTH)/2-200/2 + 20, this.getHeight()/2-100/2+75);}
				else if (board.getWinner() == 1){
					g.setColor(Color.WHITE);
					g.drawString("White Win!",
						PLAY_FIELD_WIDTH + (this.getWidth()-PLAY_FIELD_WIDTH)/2-200/2 + 10, this.getHeight()/2-100/2+75);
				}
				else{
					g.setColor(Color.CYAN);
					g.drawString("Tie...!",
							PLAY_FIELD_WIDTH + (this.getWidth()-PLAY_FIELD_WIDTH)/2-200/2 + 10, this.getHeight()/2-100/2+75);
				}
			}
		}
		private void addButtons() {
			
		}
		public void mouseMoved(MouseEvent evt) {
			
		}
		public void mouseDragged(MouseEvent evt) {
			// Do nothing
		}
		public void mouseClicked(MouseEvent evt) {
			// Do nothing
		}
		public void mouseEntered(MouseEvent evt) {
			// Do nothing	
		}
		public void mouseExited(MouseEvent evt) {
			// Do nothing	
		}
		public void mousePressed(MouseEvent evt) {
			// Do nothing	
		}
		public void mouseReleased(MouseEvent evt) {
			if(availableBlocks.contains(getBlockByCoord(evt.getX(),evt.getY()))){
				controller.mouseLeftButtonClickedInsideBoardGame(getBlockByCoord(evt.getX(),evt.getY()));
				hitSuspend = false;
				panelGame.repaint();
			}
	
		}
		public void keyPressed(KeyEvent keyEvt) {
			if(keyEvt.getKeyCode() == KeyEvent.VK_UP) {
				keyState_up = true;
			}
			else if(keyEvt.getKeyCode() == KeyEvent.VK_DOWN) {
				keyState_down = true;
			}

			if(keyEvt.getKeyCode() == KeyEvent.VK_LEFT) {
				keyState_left = true;
			}
			else if(keyEvt.getKeyCode() == KeyEvent.VK_RIGHT) {
				keyState_right = true;
			}
		}
		public void keyReleased(KeyEvent keyEvt) {
			if(keyEvt.getKeyCode() == KeyEvent.VK_UP) {
				keyState_up = false;
			}
			else if(keyEvt.getKeyCode() == KeyEvent.VK_DOWN) {
				keyState_down = false;
			}

			if(keyEvt.getKeyCode() == KeyEvent.VK_LEFT) {
				keyState_left = false;
			}
			else if(keyEvt.getKeyCode() == KeyEvent.VK_RIGHT) {
				keyState_right = false;
			}
			else if(keyEvt.getKeyCode() == KeyEvent.VK_ESCAPE) {
				/*if(isMenuOn)
					isMenuOn = false;
				else if(!isMenuOn)
					isMenuOn = true;*/
			}
		}
		public void keyTyped(KeyEvent arg0) {
			// Do nothing
		}
		
		private Point getBlockByCoord(int x, int y){
			Point clicked = new Point();
			int counter = 1;
			if(x>TABLE_X+(TABLE_SIZE-FIELD_SIZE)/2&&x<TABLE_X+TABLE_SIZE-(TABLE_SIZE-FIELD_SIZE)/2
					&&y>TABLE_Y+(TABLE_SIZE-FIELD_SIZE)/2&&y<TABLE_Y+TABLE_SIZE-(TABLE_SIZE-FIELD_SIZE)/2){
				for(int i=TABLE_X+(TABLE_SIZE-FIELD_SIZE)/2;i<TABLE_X+TABLE_SIZE-(TABLE_SIZE-FIELD_SIZE)/2;i+=BLOCK_SIZE){
					if(x>i&&x<i+BLOCK_SIZE){
						clicked.x = counter;
						counter = 1;
						break;
					}
					counter++;
				}
				for(int i=TABLE_Y+(TABLE_SIZE-FIELD_SIZE)/2;i<TABLE_Y+TABLE_SIZE-(TABLE_SIZE-FIELD_SIZE)/2;i+=BLOCK_SIZE){
					if(y>i&&y<i+BLOCK_SIZE){
						clicked.y = counter;
						counter = 1;
						break;
					}
					counter++;
				}				
				// Last check	optional...
				if(clicked.x<=8&&clicked.x>=0&&clicked.y<=8&&clicked.y>=0){
					return clicked;
				}
			}
			return null;
		}
	}
	
	private class sound_Hit implements Runnable{
		public void run(){
			// Data received	
			byte[] buff = new byte[160];
			// Data format
			AudioFormat af = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, //Audio Encoding Method
					440000.0F, // Sampling number per 1sec (need to fits the import data)
					16, // bits per sample
					2, // channels (2 = stereo)
					4, // bytes per frame
					480000.0F, // frame per 1sec
					false); // ??? false is little endian.
			// Data line Object
			DataLine.Info info = new DataLine.Info(SourceDataLine.class,af);
			// Get the data line by using created line
			SourceDataLine line = null;
			try{
				line = (SourceDataLine)AudioSystem.getLine(info);
				line.open(af);
			}catch(Exception e){
			}
			line.start();	

			try{
				BufferedInputStream reader = new BufferedInputStream(new FileInputStream("./src/BGMs/hit.wav"));				

				reader.read(buff);
				reader.read(buff);
				reader.read(buff);
				if(reader.markSupported())
					reader.mark(96281);	// Apparently, the size needs to be 1 byte bigger than the Input file to mark the whole file.
				
				while(!gameover){	
					while(hitSuspend)
						try{		
							Thread.sleep(100);
						}catch(InterruptedException e){
					}
					for(int i=0;i<96280/187;i++){
						if(reader.read(buff)!=-1){
							line.write(buff,0,buff.length);}
						else
							break;
					}
					reader.reset();
					hitSuspend = true;
				}
				line.stop();
				line.close();
				reader.close();
			}catch(Exception e){
				e.printStackTrace();
			}
		}	
	}
	private class sound_Gameover implements Runnable{
		public void run(){
			// Data received
			byte[] buff = new byte[160];
			
			// Data format
			AudioFormat af = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, //Audio Encoding Method
					44000.0F, // Sampling number per 1sec (need to fits the import data)
					16, // bits per sample
					2, // channels (2 = stereo)
					4, // bytes per frame
					480000.0F, // frame per 1sec
					false); // ??? false is little endian.
			// Data line Object
			DataLine.Info info = new DataLine.Info(SourceDataLine.class,af);
			// Get the data line by using created line
			SourceDataLine line = null;
			
			try{
				line = (SourceDataLine)AudioSystem.getLine(info);
				line.open(af);
			}catch(Exception e){
				System.out.println("Error when audio output setting" + e);
				return ;
			}
			
			// Start output the data
			line.start();
			try{
				BufferedInputStream reader = new BufferedInputStream(new FileInputStream("./src/BGMs/gameover.wav"));
				while(reader.read(buff)!=-1){
					line.write(buff,0,buff.length);
				}
				Thread.sleep(5000); // Don't know why, but for safety.
				line.stop();
				line.close();
				reader.close();
			}catch(Exception e){
				e.printStackTrace();
			}
		}		
	}
	private class sound_BGMsoft implements Runnable{
		public void run(){
			// Data received
			byte[] buff = new byte[160];
			// Data format
			AudioFormat af = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, //Audio Encoding Method
					44000.0F, // Sampling number per 1sec (need to fits the import data)
					16, // bits per sample
					2, // channels (2 = stereo)
					4, // bytes per frame
					480000.0F, // frame per 1sec
					false); // ??? false is little endian.
			// Data line Object
			DataLine.Info info = new DataLine.Info(SourceDataLine.class,af);
			// Get the data line by using created line
			SourceDataLine line = null;
			try{
				line = (SourceDataLine)AudioSystem.getLine(info);
				line.open(af);
			}catch(Exception e){
				System.out.println("Error when audio output setting" + e);
				return ;
			}
			// Start output the data
			line.start();

			try{
				BufferedInputStream reader = new BufferedInputStream(new FileInputStream("./src/BGMs/gameBGMsoft.wav"));
	
				reader.read(buff);
				if(reader.markSupported())
					reader.mark(7082501);	// Apparently, the size needs to be 1 byte bigger than the Input file to mark the whole file.

				while(bgmSoft&&!gameover){
					if(board.getBlack().size()+board.getWhite().size()>50){
						bgmSoft = false;
						bgmHard = true;
					}
					if(reader.read(buff)!=-1){
						line.write(buff,0,buff.length);
					}
					else
						reader.reset();
				}
				line.stop();
				line.close();
				reader.close();
			}catch(Exception e){
				e.printStackTrace();
			}
			bgm = new Thread(new sound_BGMHard());
			bgm.start();
		}
	}
	private class sound_BGMHard implements Runnable{
		public void run(){
			// Data received
			byte[] buff = new byte[160];
			// Data format
			AudioFormat af = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, //Audio Encoding Method
					42000.0F, // Sampling number per 1sec (need to fits the import data)
					16, // bits per sample
					2, // channels (2 = stereo)
					4, // bytes per frame
					480000.0F, // frame per 1sec
					false); // ??? false is little endian.
			// Data line Object
			DataLine.Info info = new DataLine.Info(SourceDataLine.class,af);
			// Get the data line by using created line
			SourceDataLine line = null;
			try{
				line = (SourceDataLine)AudioSystem.getLine(info);
				line.open(af);
			}catch(Exception e){
			}
			FloatControl  control;
			control = (FloatControl)line.getControl(FloatControl.Type.MASTER_GAIN);

			// Start output the data
			line.start();
			control.setValue(-6);
			try{
				BufferedInputStream reader = new BufferedInputStream(new FileInputStream("./src/BGMs/gameBGMhard.wav"));
	
				reader.read(buff);
				if(reader.markSupported())
					reader.mark(10405967);	// Apparently, the size needs to be 1 byte bigger than the Input file to mark the whole file.

				while(bgmHard&&!gameover){
					if(reader.read(buff)!=-1){
						line.write(buff,0,buff.length);
					}
					else
						reader.reset();
				}
				line.stop();
				line.close();
				reader.close();
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
	/*
	 * Mutators
	 */
	public void setController(ControllerGame ctrl) {
		controller = ctrl;
	}
	
	/*
	 * Accessors
	 */
	public JPanel getPanel() {
		return panelGame;
	}
	
	public ControllerGame getController() {
		return controller;
	}
	
	@SuppressWarnings("unchecked")
	public void modelPropertyChange(final PropertyChangeEvent evt) {
		System.out.println(this.getClass().toString() + ".modelPropertyChange() called -- " + 
				evt.getPropertyName() + ": " + evt.getOldValue().toString() + 
				" -> " + evt.getNewValue().toString());
		if(evt.getPropertyName().equals("board")) {
			board = (Board) evt.getNewValue();
			
		}	
		else if(evt.getPropertyName().equals("availableBlocks")) {
			availableBlocks = (ArrayList<Point>) evt.getNewValue();
		}
		else if(evt.getPropertyName().equals("gameover")) {
			gameover = (Boolean) evt.getNewValue();
			if(!gameover){
				sound_hit.start();
				bgm.start();
			}
			else{
				bgmSoft = false;
				bgmHard = false;
				sound_gameover.start();
			}
		}
		else if(evt.getPropertyName().equals("turn"))			
			turn = (Integer) evt.getNewValue();		
		else if(evt.getPropertyName().equals("Turn_count"))
			Turn_count = (Integer) evt.getNewValue();
		else if(evt.getPropertyName().equals("gameMode"))
			gameMode = (Integer) evt.getNewValue();
	}

}
